#!/usr/bin/env python
# -*- coding: UTF8 -*-
# Copyright (c) 2006, Osvaldo Santna Neto <osantana@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither the name of the Instituto Nokia de Tecnologia nor the names of its
#       contributors may be used to endorse or promote products derived from this
#       software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

import sys
import os
import re
import time
import ConfigParser

from locale import gettext as _

try:
    import pygtk
    pygtk.require("2.0")
    import gtk
    import gtk.glade
    import gobject
except (AssertionError, ImportError), e:
    sys.stdout.write(_("""Error importing PyGTK+ 2.0 and Python Glade
support. Please, take a look at http://www.pygtk.org to download and install the
GTK+ Python bindings.
"""))
    sys.exit(1)

try:
    import gnome
except ImportError, e:
    sys.stdout.write(_("Error importing Gnome Desktop support."))
    sys.exit(1)

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

from install_info import data_dir
from xwindow import get_active_title
from util import *

app_name = "titletrack"
app_version = "0.0.1"

glade_dir = data_dir
locale_dir = ""
bindtextdomain(app_name, locale_dir)

REFRESH_RATE = 1000

class Filter(object):
    def __init__(self, name=None, rule=None, active=True, rawstr=None):
        if rawstr is not None:
            splitted = rawstr.split('\t')
            self.name = splitted[0]
            self.rule = splitted[1]
            self.active = bool(splitted[2])
        else:
            self.name = name
            self.rule = rule
            self.active = active

    def __str__(self):
        return '%s\t%s\t%s' % (self.name, self.rule, self.active)

    def match(self, title):
        return re.match(self.rule, title)

    def split_title(self, title):
        match = self.match(title)
        if match is None:
            return [title]

        group_dict = match.groupdict()

        key_order = []
        for k in group_dict:
            if not k.startswith("level"):
                continue

            sufix = k.replace("level", "")
            try:
                level_number = int(sufix)
            except ValueError, e:
                continue

            key_order.append((level_number, sufix))

        key_order.sort()

        levels = []
        for k, sufix in key_order:
            levels.append(group_dict['level' + sufix])

        return levels


class Task(object):
    def __init__(self, title):
        self.title = title
        self.time = 0

    def tick(self):
        self.time += (REFRESH_RATE / 1000.0)

    def reset(self):
        self.time = 0


class LogRegister(object):
    """There are 3 types of register:
    - task: for normal task
    - pause: for pause events
    - start: for start events"""
    def __init__(self, task=None, type_="changed"):
        self.type_ = type_
        self.task = task
        self.time = time.time()

    def __iter__(self):
        return iter((self.type_, str(self.task), str(self.time)))


class Config(object):
    def __init__(self):
        self.conf_dir = os.path.join(os.path.expanduser('~'), '.config', app_name)
        self.conf_filename = os.path.join(self.conf_dir, 'options.cfg')
        self.config_parser = ConfigParser.RawConfigParser()

        if not os.path.exists(self.conf_filename):
            self.create_config()
        else:
            self.load_config()

    def load_config(self):
        self.config_parser.read([self.conf_filename])

    def save(self):
        fd = open(self.conf_filename, 'w')
        self.config_parser.write(fd)
        fd.close()

    def create_config(self):
        if not os.path.exists(self.conf_dir):
            mkdir_p(self.conf_dir)

        self.config_parser.add_section('global')
        self.config_parser.set('global', 'systray', 0)
        self.config_parser.add_section('filters')
        self.config_parser.set('filters', 'number', 0)

        # TODO define some standard filters
        #self.config_parser.set('filters', 'blablabla', 0)

        self.save()

    def _get_systray(self):
        self.config_parser.getboolean('global', 'systray')

    def _set_systray(self, status):
        self.config_parser.set('global', 'systray', int(status))
        self.save()
    systray = property(_get_systray, _set_systray)

    def _get_filters(self):
        if self.config_parser.has_option('filters', 'number'):
            number = self.config_parser.getint('filters', 'number')
        else:
            number = 0
            self.config_parser.set('filters', 'number', number)

        filters = []
        for i in range(number):
            rawstr = self.config_parser.get('filters', 'filter%03d' % (i))
            filters.append(Filter(rawstr=rawstr))

        return filters

    def _set_filters(self, filters):
        for filter_ in self.config_parser.options('filters'):
            self.config_parser.remove_option('filters', filter_)

        for i, filter_ in enumerate(filters):
            self.config_parser.set('filters', 'filter%03d' % (i), str(filter_))

        self.config_parser.set('filters', 'number', len(filters))

        self.save()
    filters = property(_get_filters, _set_filters)

class Model(object):
    def __init__(self):
        self.tasks = {}
        self.log = []

        self.running = False
        self.last_task_title = None

        self.controller = None

        self.config = Config()

    def set_controller(self, controller):
        self.controller = controller

    def reload(self):
        pass

    def start(self):
        if self.running:
            return
        self.log.append(LogRegister(type_="start"))
        self.running = True

    def pause(self):
        if not self.running:
            return
        self.log.append(LogRegister(type_="pause"))
        self.running = False

    def reset(self):
        for task in self.tasks.values():
            self.controller.remove(task)
        self.tasks = {}

    def update(self):
        if not self.running:
            return True

        title  = get_active_title()

        try:
            task = self.tasks[title]
        except KeyError, e:
            task = Task(title)
            self.tasks[title] = task

        task.tick()
        self.controller.update(task)

        if title != self.last_task_title:
            self.log.append(LogRegister(task))
            self.last_task_title = title

        return True


class PreferenceDialog(object):
    def __init__(self):
        xml = gtk.glade.XML("%s/titletrack.glade" % (data_dir,), "preferencesdialog")

        self.dialog = xml.get_widget("preferencesdialog")
        self.dialog.hide()

        self.filters_view = xml.get_widget("filters_view")
        self.liststore = gtk.ListStore(object, str, str, bool)

        # Name col
        text_renderer = gtk.CellRendererText()
        filter_name = gtk.TreeViewColumn("Name", text_renderer, text=1)
        self.filters_view.append_column(filter_name)
        # Rule col
        text_renderer = gtk.CellRendererText()
        filter_rule = gtk.TreeViewColumn("Rule", text_renderer, text=2)
        self.filters_view.append_column(filter_rule)
        # Status col
        toggle_renderer = gtk.CellRendererToggle()
        filter_status = gtk.TreeViewColumn("Active", toggle_renderer, active=3)
        self.filters_view.append_column(filter_status)

        self.filters_view.set_model(self.liststore)

    def run(self):
        self.dialog.run()
        self.dialog.hide()


class MainTreeStoreWrapper(object):
    def __init__(self):
        self._index = {}
        self.store = gtk.TreeStore(object, str, str)

    def update(self, task):
        if task.title in self._index:
            self._index[task.title][0] = task
            self._index[task.title][1] = task.title
            self._index[task.title][2] = format_time(task.time)
        else:
            self.store.append(None, (task, task.title, format_time(task.time)))
            row = self.store[-1]
            self._index[task.title] = row

    def remove(self, task):
        del self._index[task.title]


class Controller(SimpleGladeApp):
    def __init__(self, path="titletrack.glade",
                 root="main_win",
                 domain=app_name, **kwargs):
        path = os.path.join(glade_dir, path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    def new(self):
        self.model = None
        self.main_win.connect("destroy", self.on_quit_activate)

        # creating tree
        self.treestore = MainTreeStoreWrapper()
        self.time_table.set_model(self.treestore.store)
        self.time_table.set_reorderable(False)
        self.task_rows = {}

        # Task col
        text_renderer = gtk.CellRendererText()
        task_title_col = gtk.TreeViewColumn("Task", text_renderer, text=1)
        task_title_col.set_expand(True)
        task_title_col.set_sort_column_id(1)
        self.time_table.set_search_column(1)

        # Time col
        text_renderer = gtk.CellRendererText()
        task_time_col = gtk.TreeViewColumn("Time", text_renderer, text=2)
        task_time_col.set_sort_column_id(2)
        self.time_table.append_column(task_title_col)
        self.time_table.append_column(task_time_col)

        # export dialog
        xml = gtk.glade.XML("%s/titletrack.glade" % (data_dir,), "exportdialog")
        self.exportdialog = xml.get_widget("exportdialog")
        self.exportdialog.hide()

    def set_model(self, model):
        self.model = model
        self.model.set_controller(self)

    def on_reset_activate(self, widget, *args):
        self.model.reset()

    def on_export_activate(self, widget, *args):
        self.exportdialog.run()
        self.exportdialog.hide()

    def on_preferences_activate(self, widget, *args):
        self.preferences.run()

    def on_about_activate(self, widget, *args):
        xml = gtk.glade.XML("%s/titletrack.glade" % (data_dir,), "aboutdialog")
        self.aboutdialog = xml.get_widget("aboutdialog")
        self.aboutdialog.run()
        self.aboutdialog.hide()

    def on_quit_activate(self, widget, *args):
        self.quit()

    def on_start_activate(self, widget, *args):
        self.tb_start.set_active(True)
        self.model.start()

    def on_pause_activate(self, widget, *args):
        self.tb_pause.set_active(True)
        self.model.pause()

    def on_start_clicked(self, widget, *args):
        self.model.start()

    def on_pause_clicked(self, widget, *args):
        self.model.pause()

    def update(self, task):
        self.treestore.update(task)

    def remove(self, task):
        self.treestore.remove(task)

def main():
    gnome.program_init("titletrack", "0.0.1")

    controller = Controller()
    model = Model()
    controller.set_model(model)

    gobject.timeout_add(REFRESH_RATE, model.update) # 1 second!

    controller.run()
